﻿using System;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Reflection;
using EmbeddedLanguage.Attributes;
using EmbeddedLanguage.SubProcessors;

namespace EmbeddedLanguage
{
    public partial class ExpressionToJSProcessor
    {

        public const string Comment_VisitMember_Start = "VisitMember.Start";
        public const string Comment_VisitMember_do = "VisitMember.do";

        #region Common Visit Methods
        protected virtual void AddArgsToNestedString(NestedString ns, ReadOnlyCollection<Expression> node_Arguments)
        {
            this.AddArgsToNestedString(ns, node_Arguments, null);
        }

        protected virtual void AddArgsToNestedString(NestedString ns, ReadOnlyCollection<Expression> node_Arguments, Func<int, ExpressionToJSProcessor> CustomProcessors)
        {
            if (node_Arguments == null) return;
            ns.ChildSeparator = ",";
            ns.ChildSeparatorFollowingLastElement = string.Empty;
            ns.ChildrenContinueOnLine = true;
            var current = this.CurrentNestedString;
            this.CurrentNestedString = ns;
            int i = 0;
            foreach (var arg in node_Arguments)
            {
                if (CustomProcessors != null)
                {
                    var processor = CustomProcessors(i);
                    if (processor != null)
                    {
                        this.TransferState(processor);
                        processor.Visit(arg);
                    }
                    else
                    {
                        this.Visit(arg);
                    }
                }
                else
                {
                    this.Visit(arg);
                }
                i++;
            }
            this.CurrentNestedString = current;
        }



        protected virtual Expression VisitMethodOrDelegateCall(Expression node,
            Expression node_Object, MemberInfo node_Method, ReadOnlyCollection<Expression> node_Arguments,
            bool FromInvocation)
        {

            var ns = new NestedString
            {
                Parent = this.CurrentNestedString,
                Comment = "VisitMethodOrDelegateCall.Start",
            };
            this.DetermineIfKaboose(ns);

            if (node_Object == null)
            {

                var standardLib = node_Method.GetAttribute<StandardLibraryMappingAttribute>();
                string methodName = (standardLib == null) ?
                    GetFullName(node_Method.DeclaringType) + "." + node_Method.Name :
                    standardLib.JSFunctionName;
                ns.LHS = methodName + "(";
                ns.RHS = ")";
                this.AddArgsToNestedString(ns, node_Arguments);
            }
            else
            {
                this.CurrentNestedString = ns;
                if (ns.IsKaboose)
                {
                    ns.IsKabooseTransferrableToChild = true;
                }
                this.Visit(node_Object);
                //this.DetermineIfKaboose(ns);

                string LHSVar = ns.IsKaboose ? ns.TrainOfMethodsParameter : string.Empty;
                var keyWrd = node_Method.GetAttribute<KeywordSubstitutionAttribute>();
                if (keyWrd != null)
                {
                    var nsKeyWrd = new NestedString
                    {
                        Parent = ns,
                        RHS = keyWrd.JSKeyword + (ns.IsKaboose ? ";" : string.Empty),
                        Comment = "VisitMethodOrDelegateCall.Keyword",
                    };
                }
                else
                {
                    string methodName = node_Method.Name;
                    
                    var nsMethodCall = new NestedString
                    {
                        Parent = ns,
                        LHS = LHSVar + (FromInvocation ? string.Empty : "." + methodName) + "(",
                        //if from invocation, will need to wrap parenthesis first
                        RHS = ")" + ((ns.IsKaboose && !FromInvocation) ? ";" : string.Empty),
                        Comment = "VisitMethodOrDelegateCall.MethodCall",
                    };
                    
                    
                    this.DetermineIfKaboose(nsMethodCall);
                    bool isDoFuncCall = false;
                    NestedString prevSibling = null;
                    if (FromInvocation)
                    {
                        prevSibling = this.GetPreviousSibling(nsMethodCall);
                        if (prevSibling != null)
                        {
                            if (prevSibling.Comment.StartsWith(Comment_VisitMember_Start))
                            {
                                if (prevSibling.RHS.EndsWith(";"))
                                {
                                    prevSibling.RHS = prevSibling.RHS.SubstringBeforeLast(";");
                                    if (nsMethodCall.IsKaboose)
                                    {
                                        if (!nsMethodCall.RHS.EndsWith(";"))
                                        {
                                            nsMethodCall.RHS += ";";
                                        }
                                    }
                                }
                            }
                            else if (prevSibling.Comment == Comment_VisitMember_do)
                            {
                                isDoFuncCall = true;
                                nsMethodCall.LHS = nsMethodCall.LHS.SubstringBeforeLast("(");
                                nsMethodCall.RHS = nsMethodCall.RHS.Substring(1);
                            }
                        }
                    }
                    if (nsMethodCall.IsKaboose)
                    {
                        if (nsMethodCall.Footer != null)
                        {
                            nsMethodCall.ForceLineBreakOnFooter = true;
                        }
                    }
                    if (isDoFuncCall)
                    {
                        this.AddArgsToNestedString(prevSibling, node_Arguments);
                    }
                    else
                    {
                        this.AddArgsToNestedString(nsMethodCall, node_Arguments);
                    }
                }
            }
            this.CurrentNestedString = ns.Parent;
            return node;

        }
        #endregion
    }
}
